//
// Created on 2025/7/25.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include "native/safe_file_handle.h"
#include <cerrno>
#include <fcntl.h>
#include <poll.h>
#include <sys/file.h>
#include <system_error>
#include <unistd.h>

SafeFileHandle::SafeFileHandle(std::string path, FileMode mode, FileAccess access, FileShare share)
    : fd_(-1), path_(path), is_locked_(false), delete_on_close_(false), length_(0), position_(0), canSeek_(false),
      access_(access) {
    int flags = O_RDONLY;
    int permission = 0666;


    switch (mode) {
    default:
    case FileMode::Open:
        break;
    case FileMode::CreateNew:
        flags |= (O_CREAT | O_EXCL);
        break;
    case FileMode::Create:
        flags |= (O_CREAT | O_TRUNC);
        break;
    case FileMode::Truncate:
        flags |= O_TRUNC;
        break;
    case FileMode::Append:
    case FileMode::OpenOrCreate:
        flags |= O_CREAT;
        break;
    }

    switch (access) {
    case FileAccess::Read:
        flags |= O_RDONLY;
        break;
    case FileAccess::ReadWrite:
        flags |= O_RDWR;
        break;
    case FileAccess::Write:
        flags |= O_WRONLY;
        break;
    }

    if (static_cast<int>(share) & static_cast<int>(FileShare::Inheritable)) {
        flags |= O_CLOEXEC;
    }


    int fd = open(path.c_str(), flags, permission);

    if (fd == -1) {
        throw std::system_error(errno, std::system_category(), "failed to open file");
    }

    struct stat st;

    if (-1 == fstat(fd, &st)) {
        ::close(fd);
        throw std::system_error(errno, std::system_category(), "fstat failed");
    }

    if ((st.st_mode & S_IFMT) != S_IFREG) {
        ::close(fd);
        throw std::system_error(EACCES, std::system_category(), "not support open dir.");
    }

    // 验证是否可以seek
    if (-1 != lseek(fd, 0, SEEK_CUR)) {
        canSeek_ = true;
    }

    length_ = st.st_size;

    if (mode == FileMode::Append) {
        position_ = length_;
        append_start_ = length_;
    } else {
        append_start_ = -1;
    }

    int locktype = share == FileShare::None ? LOCK_EX : LOCK_SH;

    lock(locktype);

    fd_ = fd;
}


SafeFileHandle::~SafeFileHandle() { release(); }

SafeFileHandle::SafeFileHandle(SafeFileHandle &&other) noexcept
    : fd_(std::exchange(other.fd_, -1)), path_(std::move(other.path_)),
      delete_on_close_(std::exchange(other.delete_on_close_, false)),
      is_locked_(std::exchange(other.is_locked_, false)) {}

SafeFileHandle &SafeFileHandle::operator=(SafeFileHandle &&other) noexcept {
    if (this == &other) {
        return *this;
    }

    fd_ = std::exchange(other.fd_, -1);
    path_ = std::move(other.path_);
    delete_on_close_ = std::exchange(other.delete_on_close_, false);
    is_locked_ = std::exchange(other.is_locked_, false);
    return *this;
}


void SafeFileHandle::lock(int locktype) {
    if (fd_ != -1 && !is_locked_) {
        flock(fd_, locktype | LOCK_NB);
        is_locked_ = true;
    }
}

void SafeFileHandle::unlock() {
    if (is_locked_) {
        flock(fd_, LOCK_UN);
        is_locked_ = false;
    }
}


void SafeFileHandle::release() {
    if (fd_ != -1) {
        if (delete_on_close_) {
            unlink(path_.c_str());
        }
        unlock();
        close(fd_);
        fd_ = -1;
    }
}

void SafeFileHandle::write(const void *buf, size_t len) {
    if (!buf && len > 0) {
        throw std::invalid_argument("Buffer is null but length > 0");
    }
    if (fd_ == -1) {
        throw std::runtime_error("Invalid file descriptor");
    }
    const char *buffer = static_cast<const char *>(buf);
    try {
        while (len > 0) {
            ssize_t written = ::pwrite(fd_, buffer, len, position_);
            if (written == -1) {
                switch (errno) {
                case EINTR:
                    continue;
                case EAGAIN: {
                    struct pollfd pfd = {fd_, POLLOUT, 0};
                    if (poll(&pfd, 1, -1) == -1) {
                        throw std::system_error(errno, std::system_category(), "poll failed");
                    }
                    continue;
                }
                case ENOSPC:
                    throw std::runtime_error("No space left on device");
                case EACCES:
                    throw std::runtime_error("Permission denied");
                case EBADF:
                    throw std::runtime_error("Bad file descriptor");
                default:
                    throw std::system_error(errno, std::generic_category(), "write failed");
                }
            }
            position_ += written;
            buffer += written;
            len -= written;
        }
        if (position_ > length_) {
            length_ = position_;
        }
    } catch (const std::exception &e) {
        if (position_ > length_) {
            length_ = position_;
        }
        throw;
    }


    if (position_ > length_) {
        length_ = position_;
    }
}

long SafeFileHandle::read(void *buf, size_t len) {
    char *buffer = static_cast<char *>(buf);
    while (len > 0) {
        ssize_t read_bytes = ::pread(fd_, buffer, len, position_);
        if (read_bytes == -1) {
            switch (errno) {
            case EINTR:
                continue;
            case EAGAIN: {
                struct pollfd pfd = {fd_, POLLIN, 0};
                if (poll(&pfd, 1, -1) == -1) {
                    throw std::system_error(errno, std::system_category(), "poll failed");
                }
                continue;
            }
            default:
                throw std::system_error(errno, std::system_category(), "read failed");
            }
        }
        if (read_bytes == 0)
            break; // EOF

        position_ += read_bytes;
        buffer += read_bytes;
        len -= read_bytes;
    }
    return buffer - static_cast<char *>(buf);
}

size_t SafeFileHandle::seek(long offset, int origin) {
    long old_pos = position_;

    long pos = 0;
    switch (origin) {
    case SEEK_SET:
        pos = offset;
        break;
    case SEEK_CUR:
        pos = position_ + offset;
        break;
    case SEEK_END:
        pos = length_ + offset;
        break;
    }
    if (pos >= 0) {
        position_ = pos;
    } else {
        throw std::invalid_argument("seek failed");
    }

    if (append_start_ != -1 && pos < append_start_) {
        position_ = old_pos;
        throw std::runtime_error("Append mode not support seek overwrite");
    }

    return pos;
}

void SafeFileHandle::flush() {
    if (fsync(fd_) != 0) {
        throw std::system_error(errno, std::system_category(), "fsync failed");
    }
}

void SafeFileHandle::setLength(size_t length) {
    if (-1 == ftruncate(fd_, length))
        throw std::system_error(errno, std::generic_category(), "ftruncate failed");

    if (position_ > length) {
        position_ = length;
    }
    length_ = length;
}
